package com.ctrip.corp.obt.shard.sql.parser.fuzzy;

import java.lang.reflect.Field;
import java.text.MessageFormat;
import java.util.Objects;

import javax.annotation.Nonnull;

import org.apache.ibatis.mapping.ParameterMapping;

import com.ctrip.corp.obt.generic.utils.CollectionUtils;
import com.ctrip.corp.obt.generic.utils.EncryptUtils;
import com.ctrip.corp.obt.generic.utils.JsonUtils;
import com.ctrip.corp.obt.generic.utils.ReflectionUtils;
import com.ctrip.corp.obt.generic.utils.StringUtils;
import com.ctrip.corp.obt.shard.annotation.SecurityFuzzyQueryField;
import com.ctrip.corp.obt.shard.exception.EncryptFailedException;
import com.ctrip.corp.obt.shard.exception.GetPropertyFailedException;
import com.ctrip.corp.obt.shard.exception.UnsupportedDivideLengthException;
import com.ctrip.corp.obt.shard.sql.mybatis.interceptor.inner.FuzzyQueryEncryptPlugin.FuzzyParserAdditionalRequest;
import com.ctrip.corp.obt.shard.utils.ParameterPropertyUtils;

import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.schema.Column;

/**
 * 模糊查询处理器
 *
 * @author xukang zhang
 * @since 2024-01-04
 */
@Slf4j
public class SecurityFuzzyQueryHandler {
    /**
     * 默认的分词加密列名前缀
     */
    public static final String SECRET_DIVIDE_DEFAULT_COLUMN_PREFIX = "secret_divide";
    public static final String COLUMN_JOIN = "_";
    /**
     * 多个分词的加密结果的连接符
     */
    public static final String SECRET_DIVIDE_PART_JOIN = "";
    /**
     * 对称加密密钥
     */
    private static final String ENCRYPT_KEY = "1234567890123456";
    /**
     * like的通配符，加密时会跳过通配符的加密
     */
    private static final String LIKE_WILDCARDS_1 = "%";
    private static final String LIKE_WILDCARDS_2 = "_";
    private static final String WILDCARDS_PATTERN = "(?<=%|_)(?!%|_)";


    /**
     * 计算分词列名
     *
     * @param securityFuzzyQueryField
     * @param column
     * @return
     */
    public static String buildDivideColumnName(SecurityFuzzyQueryField securityFuzzyQueryField, Column column) {
        String divideColumn = securityFuzzyQueryField.divideColumn();
        String originalColumnName = column.getColumnName();
        // 去除表名中的"`"
        originalColumnName = originalColumnName.replaceAll("`", "");
        originalColumnName = originalColumnName.replaceAll("\"", "");
        if (StringUtils.isBlank(divideColumn)) {
            // 使用默认列名 ${columnName}_secret_divide_${divideNumber}
            divideColumn =
                    originalColumnName + COLUMN_JOIN + SECRET_DIVIDE_DEFAULT_COLUMN_PREFIX + COLUMN_JOIN
                            + securityFuzzyQueryField.divideLength();
        }
        return divideColumn;
    }

    /**
     * 获取模糊查询的匹配串
     *
     * @param propertyName
     * @param parameterMapping
     * @param parameterObject
     * @param fuzzyParserAdditionalRequest
     * @return
     */
    public static String buildQueryContent(String propertyName, ParameterMapping parameterMapping,
                                           Object parameterObject, FuzzyParserAdditionalRequest fuzzyParserAdditionalRequest) {
        Object property = null;
        try {
            property = ParameterPropertyUtils.getPropertyValue(fuzzyParserAdditionalRequest, parameterMapping,
                parameterObject);
        } catch (Exception e) {
            String logFormat = MessageFormat.format("Fail to get property: {0} in parameter: {1}, with exception: ",
                    propertyName, JsonUtils.toJsonString(parameterObject));
            log.warn(logFormat, e);
            throw new GetPropertyFailedException(logFormat, e);
        }
        if (property == null) {
            log.warn("Parsing expression value encounter null property, propertyName: {}, parameterMapping: {}",
                    propertyName, JsonUtils.toJsonString(parameterMapping));
            return null;
        }
        if (!(property instanceof String)) {
            log.warn("Parsing expression value encounter property that is not String, {}",
                    JsonUtils.toJsonString(property));
            return null;
        }
        String content = (String) property;
        return content;
    }

    /**
     * 获取SecurityFuzzyQuery注解
     *
     * @param propertyName
     * @param parameterObject
     * @return
     */
    public static SecurityFuzzyQueryField buildSecurityFuzzyQuery(String propertyName, Object parameterObject) {
        Field field = null;
        try {
            field = ReflectionUtils.findField(parameterObject.getClass(), propertyName);
        } catch (Exception e) {
            log.warn("Get field of like property failed: {}", propertyName);
            String logFormat = MessageFormat.format("Fail to get property: {0} in parameter: {1}, with exception: ",
                    propertyName, JsonUtils.toJsonString(parameterObject));
            throw new GetPropertyFailedException(logFormat, e);
        }
        if (field == null) {
            log.warn("Not found field: {}, in parameterObject: {}", propertyName,
                    JsonUtils.toJsonString(parameterObject));
            return null;
        }
        SecurityFuzzyQueryField securityFuzzyQueryField = field.getAnnotation(SecurityFuzzyQueryField.class);
        if (securityFuzzyQueryField == null) {
            // 无注解，不进行处理
            return null;
        }
        return securityFuzzyQueryField;
    }
    

    /**
     * 通过指定的算法名和分词长度进行加密
     *
     * @param content
     * @param encryptAlgorithm
     * @param divideLength
     * @param ignoreWildCard   是否忽略通配符
     * @return
     */
    public static String encryptWithDividing(String content, @Nonnull String encryptAlgorithm, int divideLength,
                                      boolean ignoreWildCard) {
        if (StringUtils.isBlank(content)) {
            return content;
        }
        if (content.length() < divideLength) {
            // 小于分词长度，不进行加密
            log.warn("The content[{}] length is less than divideLength[{}], skip encrypt when fuzzy querying", content,
                    divideLength);
            return content;
        }
        // 确定加密算法
        EncryptFunction encryptFunction = null;
        switch (encryptAlgorithm) {
            case "sm4":
                encryptFunction = EncryptUtils::sm4Encrypt;
        }
        if (encryptFunction == null) {
            log.error("No matched encrypt algorithm when fuzzy querying: {}", encryptAlgorithm);
            return content;
        }

        // 以divideNumber为分词长度加密
        if (ignoreWildCard) {
            // 跳过通配符
            return encryptSkipWildCard(content, encryptFunction, divideLength);
        }
        return encryptWithDividingInner(content, encryptFunction, divideLength);
    }

    /**
     * 通过指定的算法名和分词长度进行加密(不忽略通配符)
     *
     * @param content
     * @param encryptAlgorithm
     * @param divideLength
     * @return
     */
    public static String encryptWithDividing(String content, @Nonnull String encryptAlgorithm, int divideLength) {
        return encryptWithDividing(content, encryptAlgorithm, divideLength, false);
    }

    /**
     * 跳过模糊搜索通配符加密
     *
     * @return
     */
    public static String encryptSkipWildCard(String content, EncryptFunction encryptAlgorithm, int divideLength) {
        // 按照 "%" 或者 "_" 进行分隔
        String[] groups = content.split(WILDCARDS_PATTERN);
        if (CollectionUtils.isEmpty(groups)) {
            return "";
        }
        // 对每个分组进行加密
        StringBuilder encryptedContextSb = new StringBuilder();
        for (String group : groups) {
            if (StringUtils.isBlank(group)) {
                continue;
            }
            String encryptedPart = "";
            int wildIndex1 = group.indexOf(LIKE_WILDCARDS_1);
            int wildIndex2 = group.indexOf(LIKE_WILDCARDS_2);
            if (wildIndex1 == -1 && wildIndex2 == -1) {
                // 无通配符，直接加密
                encryptedPart = encryptWithDividingInner(group, encryptAlgorithm, divideLength);
                encryptedContextSb.append(encryptedPart);
                continue;
            }
            if (wildIndex1 != -1 && wildIndex2 != -1) {
                log.warn("Unexpected wildCard index: {},{} in group: {}", wildIndex1, wildIndex2, group);
                return "";
            }
            if (wildIndex2 != -1) {
                wildIndex1 = wildIndex2;
            }
            if (group.length() == 1) {
                encryptedContextSb.append(group);
                continue;
            }
            if (wildIndex1 == 0) {
                encryptedContextSb.append(group.charAt(0));

                encryptedPart = group.substring(1);
                encryptedPart = encryptWithDividingInner(encryptedPart, encryptAlgorithm, divideLength);
                encryptedContextSb.append(encryptedPart);

            } else if (wildIndex1 == group.length() - 1) {
                encryptedPart = group.substring(0, group.length() - 1);
                encryptedPart = encryptWithDividingInner(encryptedPart, encryptAlgorithm, divideLength);
                encryptedContextSb.append(encryptedPart);

                encryptedContextSb.append(group.charAt(group.length() - 1));
            } else {
                log.warn("Unexpected wildCard index: {} in group: {}", wildIndex1, group);
                return "";
            }
        }
        return encryptedContextSb.toString();
    }

    /**
     * 加密并校验分词长度
     *
     * @param content
     * @param encryptAlgorithm
     * @param divideLength
     * @return
     */
    public static String encryptWithDividingInner(String content, EncryptFunction encryptAlgorithm, int divideLength) {
        if (StringUtils.isBlank(content)) {
            return content;
        }
        if (content.length() < divideLength) {
            // 小于分词长度，不进行加密
            String logFormat = MessageFormat.format(
                    "The content[{}] length is less than divideLength[{}], skip encrypt when fuzzy querying", content,
                    divideLength);
            log.warn(logFormat);
            throw new UnsupportedDivideLengthException(logFormat);
        }
        String[] contentArray = new String[content.length() - divideLength + 1];
        try {
            for (int i = 0; i < contentArray.length; i++) {
                String dividePart = content.substring(i, i + divideLength);
                String encrypt = encryptAlgorithm.encrypt(dividePart, ENCRYPT_KEY);
                if (Objects.equals(encrypt, dividePart)) {
                    String logFormat = MessageFormat.format("Encrypt failed when fuzzy querying: {}", dividePart);
                    log.error(logFormat);
                    throw new EncryptFailedException(logFormat);
                }
                contentArray[i] = encrypt;
            }
        } catch (Exception e) {
            log.error("Encrypt failed with exception when fuzzy querying: ", e);
            throw new EncryptFailedException(e);
        }
        // 拼接各个分词的加密
        return String.join(SECRET_DIVIDE_PART_JOIN, contentArray);
    }


    /**
     * 加密function
     */
    public interface EncryptFunction {

        String encrypt(String content, String key);
    }
}